#pragma once
#include "DataConversion.h"
#include <vcclr.h>
#include <gcroot.h>
#include "MAIMPFileInfoDynamicReadOnly.h"

namespace dotNetInteropPlugin {
	namespace PluginDev {
		using namespace System;
		using namespace System::Runtime::InteropServices;
	

	template <class ManagedType>
	class ManagedWrapperForAddr
	{
	public:
		ManagedWrapperForAddr(ManagedType elem)
		{
			Value = elem;
		}
		gcroot<ManagedType> Value;

		~ManagedWrapperForAddr()
		{
			delete Value;
		}
	};

	typedef ManagedWrapperForAddr<System::Func<IMAIMPFileInfoReadOnly^, Boolean>^> RemoveByFilterWrapper;
	typedef ManagedWrapperForAddr<System::Func<IMAIMPFileInfoReadOnly^, IMAIMPFileInfoReadOnly^, Int32>^> SortByFilterWrapper;

	class FilterOperations
	{
	public:
		static bool WINAPI DeleteFnc(AIMP2FileInfo* AFileInfo, void* AUserData)
		{
			RemoveByFilterWrapper* mgdel = (RemoveByFilterWrapper*)(AUserData);	
			return mgdel->Value->Invoke(gcnew MAIMPFileInfoDynamicReadOnly(AFileInfo));
		}
		static int WINAPI SortFnc(AIMP2FileInfo* AFileInfo1, AIMP2FileInfo* AFileInfo2, void* AUserData)
		{
			SortByFilterWrapper* mgdel = (SortByFilterWrapper*)(AUserData);	
			int tttt = mgdel->Value->Invoke(gcnew MAIMPFileInfoDynamicReadOnly(AFileInfo1), gcnew MAIMPFileInfoDynamicReadOnly(AFileInfo2));
			return tttt;
		}
	};

	//template <class TConvAlloc>
	private ref class MAIMPConnectedPlaylistConcrete sealed: public MAIMPConnectedPlaylist
	{
	internal:
		MAIMPConnectedPlaylistConcrete(int PlsID, IAIMP2Controller* Ctrl, IAIMP2PlaylistManager2* PlsM, IAIMP2Player* plr)
		{
			inPlsID = PlsID;
			inAIMPCtrl = Ctrl;
			Ctrl->AddRef();
			inPlsMan = PlsM;
			PlsM->AddRef();
			inAimpPlayer = plr;
			plr->AddRef();


			//inTempStrings = NULL;
		}
		

		~MAIMPConnectedPlaylistConcrete()
		{
			//if (inTempStrings != NULL)
			//{
			//	inTempStrings->Release();
			//	inTempStrings = NULL;
			//}
			if (inAimpPlayer != NULL)
			{
				inAimpPlayer->Release();
				inAimpPlayer = NULL;
			}
			if (inPlsMan != NULL)
			{
				inPlsMan->Release();
				inPlsMan = NULL;
			}
			if (inAIMPCtrl != NULL)
			{
				inAIMPCtrl->Release();
				inAIMPCtrl = NULL;
			}
		}

		virtual property Int32 PlaylistID
		{
			Int32 get() override
			{
				return inPlsID;
			}
		}

	private:
		IAIMP2Controller* inAIMPCtrl;
		IAIMP2PlaylistManager2* inPlsMan;
		IAIMP2Player* inAimpPlayer;
		//IPLSStrings* inTempStrings;
		int inPlsID;

		typedef StaticSingleThreadAllocator TConvAlloc;
		typedef FileInfoManUnmanConverter<TConvAlloc> TemporyFInfConverter;

	public:
		// ============= IMAIMPPlaylist =================
		virtual void RemoveByFilter(System::Func<IMAIMPFileInfoReadOnly^, Boolean>^ filter) override
		{
			RemoveByFilterWrapper FncAddr(filter);
			inPlsMan->AIMP_PLS_DeleteByFilter(inPlsID, FilterOperations::DeleteFnc, &FncAddr);
		}
		virtual void SortByFilter(System::Func<IMAIMPFileInfoReadOnly^, IMAIMPFileInfoReadOnly^, Int32>^ filter) override
		{
			SortByFilterWrapper FncAddr(filter);
			inPlsMan->AIMP_PLS_SortByFilter(inPlsID, FilterOperations::SortFnc, &FncAddr);
		}
		virtual void Sort(AIMPPlaylistSortType SortType) override
		{
			inPlsMan->AIMP_PLS_Sort(inPlsID, (Int32)SortType);
		}
		virtual Boolean RemoveAt(Int32 ID) override
		{
			return inPlsMan->AIMP_PLS_Entry_Delete(inPlsID, ID);
		}
		virtual Boolean Move(Int32 FromId, Int32 ToId) override
		{
			return inPlsMan->AIMP_PLS_Entry_SetPosition(inPlsID, FromId, ToId);
		}
		virtual Boolean Add(String^ FileName) override
		{
			return Add(FileName, true);
		}
		virtual Boolean Add(IMAIMPFileInfoReadOnly^ finf) override
		{
			IPLSStrings* inTempStrings = NULL;
			inAIMPCtrl->AIMP_NewStrings((void**)&inTempStrings);

			AIMP2FileInfo* prSt = TemporyFInfConverter::GetUnmanagedFromManaged(finf);
			bool res = inTempStrings->AddFile(prSt->sFileName, prSt);
			if (res)
				res = inAIMPCtrl->AIMP_PLS_AddFiles(inPlsID, inTempStrings);
			inTempStrings->Release();
			TemporyFInfConverter::DisposeStructure(prSt);
			return res;
		}
		virtual void AddRange(System::Collections::Generic::IEnumerable<String^>^ Files) override
		{
			AddRange(Files, true);
		}
		virtual void AddRange(System::Collections::Generic::IEnumerable<IMAIMPFileInfoReadOnly^>^ Files) override
		{
			IPLSStrings* inTempStrings = NULL;
			inAIMPCtrl->AIMP_NewStrings((void**)&inTempStrings);

			System::Collections::Generic::IEnumerator<IMAIMPFileInfoReadOnly^>^ FEn = Files->GetEnumerator();
			AIMP2FileInfo* prSt = NULL;
			//int ProcessedCnt = 0;

			while (FEn->MoveNext())
			{
				prSt = TemporyFInfConverter::GetUnmanagedFromManaged(FEn->Current);
				inTempStrings->AddFile(prSt->sFileName, prSt);
				TemporyFInfConverter::DisposeStructure(prSt);
				//System::Windows::Forms::Application::DoEvents();
			}
			inAIMPCtrl->AIMP_PLS_AddFiles(inPlsID, inTempStrings);
			inTempStrings->Release();
		}
		virtual Boolean Insert(Int32 Pos, String^ FileName) override
		{
			return Insert(Pos, FileName, true);
		}
		virtual Boolean Insert(Int32 Pos, IMAIMPFileInfoReadOnly^ finf) override
		{
			if (Add(finf))
				return Move(Count-1, Pos);
			return false;
		}
		virtual void Clear() override
		{
			inPlsMan->AIMP_PLS_Entry_DeleteAll(inPlsID);
		}

		virtual String^ GetTrackTitle(Int32 ID) override
		{
			const int AllocSize = 2*256;
			PWCHAR AllocMem = (PWCHAR)TConvAlloc::Allocate(AllocSize);
			AllocMem[AllocSize/2 - 1] = 0;
			String^ res = nullptr;
			if (SUCCEEDED(inPlsMan->AIMP_PLS_Entry_GetTitle(inPlsID, ID, AllocMem, AllocSize/2 - 1)))
				res = gcnew String(AllocMem);
			TConvAlloc::Dispose((char*)AllocMem);
			return res;
		}

		virtual String^ GetTrackFileName(Int32 ID) override
		{
			const int AllocSize = 2*1024;
			PWCHAR AllocMem = (PWCHAR)TConvAlloc::Allocate(AllocSize);
			AllocMem[AllocSize/2 - 1] = 0;
			String^ res = nullptr;
			if (SUCCEEDED(inPlsMan->AIMP_PLS_Entry_FileNameGet(inPlsID, ID, AllocMem, AllocSize/2 - 1)))
				res = gcnew String(AllocMem);
			TConvAlloc::Dispose((char*)AllocMem);
			return res;
		}
		virtual void SetTrackFileName(Int32 ID, String^ Val) override
		{
			SetTrackFileName(ID, Val, true);
		}
		
		virtual MAIMPFileInfo^ GetTrackInformation(Int32 ID) override
		{
			MAIMPFileInfo^ res = nullptr;
			AIMP2FileInfo* prSt = TemporyFInfConverter::GetPreparedStructure();
			if (inPlsMan->AIMP_PLS_Entry_InfoGet(inPlsID, ID, prSt))
				res = TemporyFInfConverter::GetManagedFromUnmanaged(prSt);
			TemporyFInfConverter::DisposeStructure(prSt);
			return res;
		}
		virtual void SetTrackInformation(Int32 ID, IMAIMPFileInfoReadOnly^ Val) override
		{
			AIMP2FileInfo* prSt = TemporyFInfConverter::GetUnmanagedFromManaged(Val);
			SetTrackFileName(ID, Val->FileName, false);
			inPlsMan->AIMP_PLS_Entry_InfoSet(inPlsID, ID, prSt);
			TemporyFInfConverter::DisposeStructure(prSt);
		}
		virtual void SetOnlyTagInformation(Int32 ID, IMAIMPFileInfoReadOnly^ finf) override
		{
			AIMP2FileInfo* prSt = TemporyFInfConverter::GetUnmanagedFromManaged(finf);
			inPlsMan->AIMP_PLS_Entry_InfoSet(inPlsID, ID, prSt);
			TemporyFInfConverter::DisposeStructure(prSt);
		}
	 
		virtual Boolean GetTrackActivationSwitches(Int32 ID) override
		{
			return inPlsMan->AIMP_PLS_Entry_PlayingSwitchGet(inPlsID, ID);
		}
		virtual void SetTrackActivationSwitches(Int32 ID, Boolean Val) override
		{
			inPlsMan->AIMP_PLS_Entry_PlayingSwitchSet(inPlsID, ID, Val);
		}

		//property Boolean TrackActivationSwitches[Int32]
		//{
		//	Boolean get(Int32 ID)
		//	{
		//		return inPlsMan->AIMP_PLS_Entry_PlayingSwitchGet(inPlsID, ID);
		//	}
		//	void set(Int32 ID, Boolean Val)
		//	{
		//		inPlsMan->AIMP_PLS_Entry_PlayingSwitchSet(inPlsID, ID, Val);
		//	}
		//}
	
		virtual property Int32 Count
		{
			Int32 get() override
			{
				return inPlsMan->AIMP_PLS_GetFilesCount(inPlsID);
			}
		}

		virtual property String^ PlaylistName
		{
			String^ get() override
			{
				const int AllocSize = 2*1024;
				PWCHAR AllocMem = (PWCHAR)TConvAlloc::Allocate(AllocSize);
				AllocMem[AllocSize/2 - 1] = 0;
				String^ res = nullptr;
				if (SUCCEEDED(inPlsMan->AIMP_PLS_GetName(inPlsID, AllocMem, AllocSize/2 - 1)))
					res = gcnew String(AllocMem);
				TConvAlloc::Dispose((char*)AllocMem);
				return res;
			}
		}

		//virtual property Boolean IsConnected
		//{
		//	Boolean get() = IMAIMPPlaylist::IsConnected::get
		//	{
		//		return true;
		//	}
		//}
		//=============================
		virtual property Int32 SelectedTrack
		{
			Int32 get() override
			{
				return inPlsMan->AIMP_PLS_Entry_FocusedGet(inPlsID);
			}
			void set(Int32 val) override
			{
				inPlsMan->AIMP_PLS_Entry_FocusedSet(inPlsID, val);
			}
		}

		virtual Boolean ReloadTrackInfo(Int32 ID) override
		{
			return inPlsMan->AIMP_PLS_Entry_ReloadInfo(inPlsID, ID);
		}

		virtual void ReloadFullPlaylistInfo() override
		{
			for (int i = 0; i < Count; i++)
				inPlsMan->AIMP_PLS_Entry_ReloadInfo(inPlsID, i);
		}

		virtual void SavePlaylist(String^ FileName) override
		{
			DWORD streamIndex = 0;
			{
				pin_ptr<const WCHAR> strData = PtrToStringChars(FileName);
				streamIndex = inPlsMan->AIMP_PM_SaveStream((PWCHAR)strData);
			}

			if (streamIndex != 0)
			{
				AIMP2FileInfo* prSt = TemporyFInfConverter::GetPreparedStructure();

				for (int i = 0; i < Count; i++)
				{
					inPlsMan->AIMP_PLS_Entry_InfoGet(inPlsID, i, prSt);
					inPlsMan->AIMP_PM_WriteItem(streamIndex, prSt);
				}
				inPlsMan->AIMP_PM_DestroyStream(streamIndex);
				TemporyFInfConverter::DisposeStructure(prSt);
			}
		}

		virtual void LoadTracksFromPlaylist(String^ FileName) override
		{
			DWORD streamIndex = 0;
			int PlCount = 0;
			{
				pin_ptr<const WCHAR> strData = PtrToStringChars(FileName);
				streamIndex = inPlsMan->AIMP_PM_ReadStream((PWCHAR)strData, &PlCount);
			}

			if (streamIndex != 0)
			{
				AIMP2FileInfo* prSt = TemporyFInfConverter::GetPreparedStructure();
				IPLSStrings* inTempStrings = NULL;
				inAIMPCtrl->AIMP_NewStrings((void**)&inTempStrings);

				for (int i = 0; i < PlCount; i++)
				{
					inPlsMan->AIMP_PM_ReadItem(streamIndex, prSt);
					inTempStrings->AddFile(prSt->sFileName, prSt);
				}
				inAIMPCtrl->AIMP_PLS_AddFiles(inPlsID, inTempStrings);
				inTempStrings->Release();
				inPlsMan->AIMP_PM_DestroyStream(streamIndex);
				TemporyFInfConverter::DisposeStructure(prSt);
			}
		}

		virtual void SortByTemplate(String^ TemplateStr) override
		{
			pin_ptr<const WCHAR> strData = PtrToStringChars(TemplateStr);
			inPlsMan->AIMP_PLS_SortByTemplate(inPlsID, (PWCHAR)strData, TemplateStr->Length);
		}

		virtual Boolean Add(String^ FileName, Boolean AutoLoadInfo) override
		{
			bool res = false;
			IPLSStrings* inTempStrings = NULL;
			inAIMPCtrl->AIMP_NewStrings((void**)&inTempStrings);

			AIMP2FileInfo* prSt = NULL;
			pin_ptr<const WCHAR> ptr = PtrToStringChars(FileName);
			if (AutoLoadInfo)
			{
				prSt = TemporyFInfConverter::GetPreparedStructure();
				if (inAIMPCtrl->AIMP_QueryInfo((PWCHAR)ptr, prSt))
					res = inTempStrings->AddFile((PWCHAR)ptr, prSt);
			}
			else
			{
				prSt = TemporyFInfConverter::GetPreparedStructureWithZeroFld(1,1,1,1,1,1);
				res = inTempStrings->AddFile((PWCHAR)ptr, prSt);
			}
			TemporyFInfConverter::DisposeStructure(prSt);
			inAIMPCtrl->AIMP_PLS_AddFiles(inPlsID, inTempStrings);
			inTempStrings->Release();

			return res;
		}

		virtual void AddRange(System::Collections::Generic::IEnumerable<String^>^ Files, Boolean AutoLoadInfo) override
		{
			IPLSStrings* inTempStrings = NULL;
			inAIMPCtrl->AIMP_NewStrings((void**)&inTempStrings);

			System::Collections::Generic::IEnumerator<String^>^ FEn = Files->GetEnumerator();
			//int ProcessedCnt = 0;

			AIMP2FileInfo* prSt = NULL;

			if (AutoLoadInfo)
			{
				prSt = TemporyFInfConverter::GetPreparedStructure();
				while (FEn->MoveNext())
				{
					pin_ptr<const WCHAR> ptr = PtrToStringChars(FEn->Current);
					if (inAIMPCtrl->AIMP_QueryInfo((PWCHAR)ptr, prSt))
						inTempStrings->AddFile((PWCHAR)ptr, prSt);
					//System::Windows::Forms::Application::DoEvents();
				}
			}
			else
			{
				prSt = TemporyFInfConverter::GetPreparedStructureWithZeroFld(1,1,1,1,1,1);
				while (FEn->MoveNext())
				{
					pin_ptr<const WCHAR> ptr = PtrToStringChars(FEn->Current);
					inTempStrings->AddFile((PWCHAR)ptr, prSt);
					//System::Windows::Forms::Application::DoEvents();
				}
			}
			

			TemporyFInfConverter::DisposeStructure(prSt);
			inAIMPCtrl->AIMP_PLS_AddFiles(inPlsID, inTempStrings);
			inTempStrings->Release();
		}

		virtual Boolean Insert(Int32 Pos, String^ FileName, Boolean AutoLoadInfo) override
		{
			if (Add(FileName, AutoLoadInfo))
				return Move(Count-1, Pos);
			return false;
		}

		virtual Boolean SetTrackFileName(Int32 ID, String^ FileName, Boolean AutoLoadInfo) override
		{
			pin_ptr<const WCHAR> ptr = PtrToStringChars(FileName);
			bool res = inPlsMan->AIMP_PLS_Entry_FileNameSet(inPlsID, ID, (PWCHAR)ptr);
			if (res && AutoLoadInfo)
				return ReloadTrackInfo(ID);
			return res;
		}

		virtual property Int32 PlayingTrackIndex
		{
			Int32 get() override
			{
				return inPlsMan->AIMP_PLS_ID_PlayingGetTrackIndex(inPlsID);
			}
			void set(Int32 val) override
			{
				inAimpPlayer->PlayTrack(inPlsID, val);
				//inPlsMan->AIMP_PLS_ID_PlayingSetTrackIndex(inPlsID, val);
			}
		}

		virtual Boolean PlayTrack(Int32 ID) override
		{
			return inAimpPlayer->PlayTrack(inPlsID, ID);
		}


		virtual property Boolean IsPlaylistActive
		{
			Boolean get() override
			{
				return inPlsMan->AIMP_PLS_ID_ActiveGet() == inPlsID;
			}
		}
		virtual property Boolean IsPlaylistPlaying
		{
			Boolean get() override
			{
				return inPlsMan->AIMP_PLS_ID_PlayingGet() == inPlsID;
			}
		}

		virtual property TimeSpan PlaylistDuration
		{
			TimeSpan get() override
			{
				INT64 dur = 0;
				INT64 size = 0;
				inPlsMan->AIMP_PLS_GetInfo(inPlsID, &dur, &size);
				return TimeSpan::FromMilliseconds(dur);
			}
		}
		virtual property Int64 PlaylistSize		
		{
			Int64 get() override
			{
				INT64 dur = 0;
				INT64 size = 0;
				inPlsMan->AIMP_PLS_GetInfo(inPlsID, &dur, &size);
				return size;
			}
		}


		virtual String^ ToString() override
		{
			return PlaylistName;
		}

		static Boolean operator==(MAIMPConnectedPlaylistConcrete^ a, MAIMPConnectedPlaylistConcrete^ b)
		{
			if (((Object^)a) != nullptr && ((Object^)b) != nullptr)
				return a->PlaylistID == b->PlaylistID;
			return ((Object^)a) == ((Object^)b);
		}
		static Boolean operator!=(MAIMPConnectedPlaylistConcrete^ a, MAIMPConnectedPlaylistConcrete^ b)
		{
			if (((Object^)a) != nullptr && ((Object^)b) != nullptr)
				return a->PlaylistID != b->PlaylistID;
			return ((Object^)a) != ((Object^)b);
		}

	};

}
}